package net.jxta.myjxta.util;

import net.jxta.ext.config.Util;
import net.jxta.impl.endpoint.IPUtils;
import net.jxta.logging.Logging;
import net.jxta.myjxta.MyJXTA;

import java.io.*;
import java.net.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

public class MultipleInstanceUtil {


    // xxx: move this to net.jxta.myjxta.resources.profile.xml
    private static final String CHARSET = "UTF-8";
    private static final int EOT = 0x04;
    private static final char NEW_LINE = '\n';
    private static final char RETURN = '\r';
    private static final String SLASH = "/";
    private static final String COLON = ":";
    private static final List<String> PAYLOAD;
    private static final Logger LOG = Logger.getLogger(MultipleInstanceUtil.class.getName());

    static {
        PAYLOAD = new ArrayList<String>();

        PAYLOAD.add(MyJXTA.GROUP_NAME);
        PAYLOAD.add(MyJXTA.GROUP_ID);
        PAYLOAD.add(MyJXTA.GROUP_DESCRIPTION);
        PAYLOAD.add(MyJXTA.GROUP_AUTO_RENDEZVOUS_PERIOD);
    }

    public static boolean isOnlyInstance(MyJXTA thisInstance) {
        File f = new File(Env.getHomeDirPath() + File.separator +
                Env.ADMINISTRATION_PORT);
        int port = -1; //Env.DEFAULT_ADMINISTRATION_PORT;

        if (f.exists()) {
            try {
                FileInputStream fis = new FileInputStream(f);
                StringBuffer sb = new StringBuffer();
                int b = -1;

                while ((b = fis.read()) > -1) {
                    sb.append((char) b);
                }

                port = Integer.parseInt(sb.toString());
            } catch (FileNotFoundException fnfe) {
                if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
                    LOG.log(Level.SEVERE, "Caught unexpected Exception", fnfe);
                }
            } catch (IOException ioe) {
                if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
                    LOG.log(Level.SEVERE, "Caught unexpected Exception", ioe);
                }
            } catch (NumberFormatException nfe) {
                if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
                    LOG.log(Level.SEVERE, "Caught unexpected Exception", nfe);
                }
            }
        }

        InetAddress lh = IPUtils.LOOPBACK;
        boolean connect = false;

        if (port > -1 &&
                !Util.isPortAvailable(lh, port)) {
            Socket s = new Socket();

            try {
                SocketAddress sa = new InetSocketAddress(lh, port);

                s.connect(sa, Env.MAX_ADMINISTRATION_TIME);
            } catch (UnknownHostException uhe) {
                if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
                    LOG.log(Level.SEVERE, "Caught unexpected Exception", uhe);
                }
            } catch (IOException ioe) {
                if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
                    LOG.log(Level.SEVERE, "Caught unexpected Exception", ioe);
                }
            }

            if (s != null &&
                    s.isConnected()) {
                try {
                    InputStream is = s.getInputStream();
                    StringBuffer sb = new StringBuffer();
                    int b = -1;
                    boolean isMyJXTA = false;

                    while (!connect &&
                            (b = is.read()) > -1 &&
                            b != EOT) {
                        if (b != NEW_LINE &&
                                b != RETURN) {
                            sb.append((char) b);
                        }

                        if (!isMyJXTA &&
                                MyJXTA.NAME.charAt(sb.length() - 1) != (char) b) {
                            break;
                        }

                        if (sb.indexOf(MyJXTA.NAME) == 0) {
                            isMyJXTA = true;
                        }

                        if (isMyJXTA) {
                            if (b == NEW_LINE) {
                                int i = sb.toString().indexOf(SLASH);

                                if (b > -1) {
                                    String mjh = sb.toString().substring(i + SLASH.length());
                                    URI u = null;

                                    try {
                                        u = new URI(mjh);
                                    } catch (Exception e) {
                                        if (Logging.SHOW_FINE &&
                                                LOG.isLoggable(Level.FINE)) {
                                            LOG.fine("invalid uri: " + mjh);
                                        }
                                    }

                                    if (u != null &&
                                            u.equals(Env.getHome())) {
                                        connect = true;
                                    }
                                }
                            }
                        }
                    }

                    if (connect) {
                        OutputStream os = s.getOutputStream();

                        for (Iterator<String> p = PAYLOAD.iterator(); p.hasNext();) {
                            String k = p.next();
                            String v = System.getProperty(k);

                            if (v != null) {
                                os.write(k.getBytes());
                                os.write(COLON.getBytes());
                                os.write(v.getBytes());
                                os.write(String.valueOf(NEW_LINE).getBytes());
                                os.write(String.valueOf(RETURN).getBytes());
                                os.flush();
                            }
                        }

                        os.write(EOT);
                        os.flush();

                        try {
                            os.close();
                        } catch (IOException ioe) {
                        }
                    }

                    try {
                        is.close();
                    } catch (IOException ioe) {
                    }
                } catch (IOException ioe) {
                    if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
                        LOG.log(Level.SEVERE, "Caught unexpected Exception", ioe);
                    }
                }
            }
        }

        if (!connect) {
            List<Integer> ports = new ArrayList<Integer>();

            for (int i = Env.DEFAULT_ADMINISTRATION_PORT;
                 i <= Env.DEFAULT_ADMINISTRATION_PORT +
                         Env.MAX_ADMINISTRATION_PORT_TRY;
                 i++) {
                ports.add(new Integer(i));
            }

            Collections.shuffle(ports);
            int i = ports.indexOf(new Integer(port));

            if (i > -1) {
                ports.add(0, new Integer(port));
            }

            boolean isListening = false;

            for (Iterator<Integer> p = ports.iterator(); !isListening &&
                    p.hasNext();) {
                port = ((p.next())).intValue();
                if (Util.isPortAvailable(lh, port)) {
                    ServerSocket s = Util.getServerSocket(lh, port);

                    if (s != null) {
                        AdministrationListener adminListener = new AdministrationListener(thisInstance,
                                s,
                                MyJXTA.VERSION);

                        thisInstance.administrationListener = adminListener;
                        thisInstance.administrationListener.start();
                        try {
                            FileOutputStream fos = new FileOutputStream(f);

                            fos.write(String.valueOf(port).getBytes(CHARSET));
                            fos.flush();
                            isListening = true;
                            try {
                                fos.close();
                            }
                            catch (IOException ioe) {
                            }
                        }
                        catch (FileNotFoundException fnfe) {
                            if (Logging.SHOW_SEVERE &&
                                    LOG.isLoggable(Level.SEVERE)) {
                                LOG.log(Level.SEVERE,
                                        "Caught unexpected Exception", fnfe);
                            }
                        }
                        catch (IOException ioe) {
                            if (Logging.SHOW_SEVERE &&
                                    LOG.isLoggable(Level.SEVERE)) {
                                LOG.log(Level.SEVERE,
                                        "Caught unexpected Exception", ioe);
                            }
                        }
                    }
                } else {
                    port = -1;
                }
            }
        }

        return !connect && port != -1;
    }

    public static boolean administration(MyJXTA p_myjxta, String message, boolean apply) {
        boolean status = false;

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info(message);
        }

        if (message != null &&
                message.length() > 0) {
            p_myjxta.getAdministrationBuffer().add(message);
        }

        if (apply) {
            for (Iterator<String> a = p_myjxta.getAdministrationBuffer() != null ?
                    p_myjxta.getAdministrationBuffer().iterator() : Collections.EMPTY_LIST.iterator();
                 a.hasNext();) {
                // todo: add payload::group
                System.out.println("payload: " + a.next());
            }

            status = true;
        }

        return !status;
    }

    public static class AdministrationListener
            extends Thread {

        public static final String HELLO = "HELLO";
        public static final String SPACE = " ";
        public static final String COLON = ":";
        public static final String SLASH = "/";

        private static final int EOT = 0x04;
        private static final char NEW_LINE = '\n';
        private static final char RETURN = '\r';
        private static final Logger LOG = Logger.getLogger(AdministrationListener.class.getName());

        private MyJXTA myjxta = null;
        private ServerSocket server = null;
        private String preamble = null;
        private boolean m_stopped = false;

        public AdministrationListener(MyJXTA myjxta, ServerSocket server, String preamble) {
            this.myjxta = myjxta;
            this.server = server;
            this.preamble = preamble;

            setDaemon(true);
            setName(getClass().getName());
        }

        public void run() {
            while (!interrupted() && !m_stopped) {
                try {
                    handle(this.server.accept());
                } catch (IOException ioe) {
                    if (!m_stopped) { //ex is normal if we are stopped
                        if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
                            LOG.log(Level.SEVERE, "Caught unexpected Exception",
                                    ioe);
                        }
                    }
                }
            }

            if (this.server != null) {
                try {
                    this.server.close();
                } catch (IOException ioe) {
                    if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
                        LOG.log(Level.SEVERE, "Caught unexpected Exception", ioe);
                    }
                }

                this.server = null;
            }
        }

        private void handle(Socket s)
                throws IOException {
            OutputStream os = s.getOutputStream();
            StringBuffer sb = new StringBuffer();

            sb.append(this.preamble).append(SLASH).append(Env.getHome()).append(NEW_LINE);

            os.write(sb.toString().getBytes());
            os.flush();

            InputStream is = s.getInputStream();
            int b = -1;

            sb.delete(0, sb.length());

            // todo: would like to iteratively process directives and
            //       report status ... but this protocol is weak at present
            List<String> directives = new ArrayList<String>();

            while ((b = is.read()) > -1 &&
                    b != EOT) {
                if (b != NEW_LINE &&
                        b != RETURN) {
                    sb.append((char) b);
                } else if (b == NEW_LINE) {
                    String m = sb.toString().trim();

                    if (m.length() > 0) {
                        directives.add(m);
                    }

                    sb.delete(0, sb.length());

//                sb.append(this.preamble + COLON + SPACE + String.valueOf(status));
//
//                os.write(sb.toString().getBytes());
//                os.flush();
//
//                sb.delete(0, sb.length());
                }
            }

            os.flush();
            os.close();

            for (Iterator<String> d = directives.iterator(); d.hasNext();) {
                administration(this.myjxta, d.next(), !d.hasNext());
            }
        }

        public void stopListener() {
            m_stopped = true;
            try {
                this.server.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


}
